/*
 * Copyright (C) 2013 Allwinnertech, kevin.z.m <kevin@allwinnertech.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * Adjustable factor-based clock implementation
 */

#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/clk-private.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/module.h>
#include "clk-sunxi.h"
#include "clk-periph.h"
#define NEW_RATE_CALULATE 1

static u8 sunxi_clk_periph_get_parent(struct clk_hw *hw)
{
	u8 parent;
	unsigned long reg, flags = 0;
	struct sunxi_clk_periph *periph = to_clk_periph(hw);

	if(!periph->mux.reg)
		return 0;

	if(periph->lock)
		spin_lock_irqsave(periph->lock, flags);

	reg = periph_readl(periph,periph->mux.reg);
	parent = GET_BITS(periph->mux.shift, periph->mux.width, reg);

	if(periph->lock)
		spin_unlock_irqrestore(periph->lock, flags);

	return parent;
}


static int sunxi_clk_periph_set_parent(struct clk_hw *hw, u8 index)
{
	unsigned long reg, flags = 0;
	struct sunxi_clk_periph *periph = to_clk_periph(hw);

	if(periph->flags & CLK_READONLY)
		return 0;

	if(!periph->mux.reg)
		return 0;

	if(periph->lock)
		spin_lock_irqsave(periph->lock, flags);

	reg = periph_readl(periph,periph->mux.reg);
	reg = SET_BITS(periph->mux.shift, periph->mux.width, reg, index);
	periph_writel(periph,reg, periph->mux.reg);

	if(periph->lock)
		spin_unlock_irqrestore(periph->lock, flags);

	return 0;
}

static int __sunxi_clk_periph_enable_shared(struct sunxi_clk_periph *periph)
{
	unsigned long reg;
	struct sunxi_clk_periph_gate *gate = &periph->gate;

	if (!periph->com_gate->val) {
		/* de-assert module */
		if (gate->reset && !(periph->flags & CLK_IGNORE_AUTORESET)
				&& IS_SHARE_RST_GATE(periph)) {
			reg = periph_readl(periph,gate->reset);
			reg = SET_BITS(gate->rst_shift, 1, reg, 1);
			periph_writel(periph,reg, gate->reset);
		}
		/* enable bus gating */
		if (gate->bus && IS_SHARE_BUS_GATE(periph)) {
			reg = periph_readl(periph, gate->bus);
			reg = SET_BITS(gate->bus_shift, 1, reg, 1);
			periph_writel(periph, reg, gate->bus);
		}

		/* enable module gating */
		if (gate->enable && IS_SHARE_MOD_GATE(periph)) {
			reg = periph_readl(periph,gate->enable);
			reg = SET_BITS(gate->enb_shift, 1, reg, 1);
			periph_writel(periph,reg, gate->enable);
		}

		/* enable dram gating */
		if (gate->dram && IS_SHARE_MBUS_GATE(periph)) {
			reg = periph_readl(periph,gate->dram);
			reg = SET_BITS(gate->ddr_shift, 1, reg, 1);
			periph_writel(periph,reg, gate->dram);
		}
	}
	periph->com_gate->val |= 1 << periph->com_gate_off;

	return 0;
}

static int __sunxi_clk_periph_enable(struct clk_hw *hw)
{
	unsigned long reg;
	struct sunxi_clk_periph *periph = to_clk_periph(hw);
	struct sunxi_clk_periph_gate *gate = &periph->gate;

	/* de-assert module */
	if(gate->reset && !(periph->flags & CLK_IGNORE_AUTORESET) && !IS_SHARE_RST_GATE(periph)) {
		reg = periph_readl(periph,gate->reset);
		reg = SET_BITS(gate->rst_shift, 1, reg, 1);
		periph_writel(periph,reg, gate->reset);
	}

	/* enable bus gating */
	if(gate->bus && !IS_SHARE_BUS_GATE(periph)) {
		reg = periph_readl(periph,gate->bus);
		reg = SET_BITS(gate->bus_shift, 1, reg, 1);
		periph_writel(periph,reg, gate->bus);
	}

	/* enable module gating */
	if(gate->enable&& !IS_SHARE_MOD_GATE(periph)) {
		reg = periph_readl(periph,gate->enable);
		if(periph->flags & CLK_REVERT_ENABLE)
			reg = SET_BITS(gate->enb_shift, 1, reg, 0);
		else
			reg = SET_BITS(gate->enb_shift, 1, reg, 1);
		periph_writel(periph,reg, gate->enable);
	}

	/* enable dram gating */
	if(gate->dram&& !IS_SHARE_MBUS_GATE(periph)) {
		reg = periph_readl(periph,gate->dram);
		reg = SET_BITS(gate->ddr_shift, 1, reg, 1);
		periph_writel(periph,reg, gate->dram);
	}

	return 0;
}
static int sunxi_clk_periph_enable(struct clk_hw *hw)
{
	unsigned long flags = 0;
	int ret = 0;
	struct sunxi_clk_periph *periph = to_clk_periph(hw);

	if(periph->flags & CLK_READONLY)
		return 0;

	if(periph->lock)
		spin_lock_irqsave(periph->lock, flags);

	/* if common gate exist, enable it first */
	if(periph->com_gate)
		ret = __sunxi_clk_periph_enable_shared(periph);
	if(!ret)
		ret = __sunxi_clk_periph_enable(hw);

	if(periph->lock)
		spin_unlock_irqrestore(periph->lock, flags);

	return ret;
}
static int __sunxi_clk_periph_is_enabled(struct clk_hw *hw)
{
	int state = 1;
	unsigned long reg;
	struct sunxi_clk_periph *periph = to_clk_periph(hw);
	struct sunxi_clk_periph_gate *gate = &periph->gate;

	/* enable bus gating */
	if(gate->bus) {
		reg = periph_readl(periph,gate->bus);
		state &= GET_BITS(gate->bus_shift, 1, reg);
	}

	/* enable module gating */
	if(gate->enable) {
		reg = periph_readl(periph,gate->enable);
		state &= GET_BITS(gate->enb_shift, 1, reg);
	}
	/* de-assert module */
	if(gate->reset) {
		reg = periph_readl(periph,gate->reset);
		state &= GET_BITS(gate->rst_shift, 1, reg);
	}

	/* enable dram gating */
	if(gate->dram) {
		reg = periph_readl(periph,gate->dram);
		state &= GET_BITS(gate->ddr_shift, 1, reg);
	}

	return state;
}
static int sunxi_clk_periph_is_enabled(struct clk_hw *hw)
{
	int state = 0;
	unsigned long flags = 0;
	struct sunxi_clk_periph *periph = to_clk_periph(hw);

	if(periph->lock)
		spin_lock_irqsave(periph->lock, flags);

	state = __sunxi_clk_periph_is_enabled(hw);

	if(periph->lock)
		spin_unlock_irqrestore(periph->lock, flags);

	return state;
}
static void __sunxi_clk_periph_disable_shared(struct sunxi_clk_periph *periph)
{
	unsigned long reg;
	struct sunxi_clk_periph_gate *gate = &periph->gate;
	if(!periph->com_gate->val)
	 return ;

	periph->com_gate->val &= ~(1 << periph->com_gate_off);

	if(!periph->com_gate->val)
	{
		/* disable dram gating */
		if(gate->dram&& IS_SHARE_MBUS_GATE(periph)) {
			reg = periph_readl(periph,gate->dram);
			reg = SET_BITS(gate->ddr_shift, 1, reg, 0);
			periph_writel(periph,reg, gate->dram);
		}

		/* disable module gating */
		if(gate->enable&& IS_SHARE_MOD_GATE(periph)) {
			reg = periph_readl(periph,gate->enable);
			reg = SET_BITS(gate->enb_shift, 1, reg, 0);
			periph_writel(periph,reg, gate->enable);
		}

		/* disable bus gating */
		if(gate->bus&& IS_SHARE_BUS_GATE(periph)) {
			reg = periph_readl(periph,gate->bus);
			reg = SET_BITS(gate->bus_shift, 1, reg, 0);
			periph_writel(periph,reg, gate->bus);
		}
		/* assert module */
		if(gate->reset && !(periph->flags & CLK_IGNORE_AUTORESET) && IS_SHARE_RST_GATE(periph)) {
			reg = periph_readl(periph,gate->reset);
			reg = SET_BITS(gate->rst_shift, 1, reg, 0);
			periph_writel(periph,reg, gate->reset);
		}
	}

}

static void __sunxi_clk_periph_disable(struct clk_hw *hw)
{
	unsigned long reg;
	struct sunxi_clk_periph *periph = to_clk_periph(hw);
	struct sunxi_clk_periph_gate *gate = &periph->gate;

	/* disable dram gating */
	if(gate->dram&& !IS_SHARE_MBUS_GATE(periph)) {
		reg = periph_readl(periph,gate->dram);
		reg = SET_BITS(gate->ddr_shift, 1, reg, 0);
		periph_writel(periph,reg, gate->dram);
	}

	/* disable module gating */
	if(gate->enable&& !IS_SHARE_MOD_GATE(periph)) {
		reg = periph_readl(periph,gate->enable);
		if(periph->flags & CLK_REVERT_ENABLE)
			reg = SET_BITS(gate->enb_shift, 1, reg, 1);
		else
			reg = SET_BITS(gate->enb_shift, 1, reg, 0);

		periph_writel(periph,reg, gate->enable);
	}

	/* disable bus gating */
	if(gate->bus&& !IS_SHARE_BUS_GATE(periph)) {
		reg = periph_readl(periph,gate->bus);
		reg = SET_BITS(gate->bus_shift, 1, reg, 0);
		periph_writel(periph,reg, gate->bus);
	}

	/* assert module */
	if(gate->reset && !(periph->flags & CLK_IGNORE_AUTORESET) &&!IS_SHARE_RST_GATE(periph)) {
		reg = periph_readl(periph,gate->reset);
		reg = SET_BITS(gate->rst_shift, 1, reg, 0);
		periph_writel(periph,reg, gate->reset);
	}
}
static void sunxi_clk_periph_disable(struct clk_hw *hw)
{
	unsigned long flags = 0;
	struct sunxi_clk_periph *periph = to_clk_periph(hw);

	if(periph->flags & CLK_READONLY)
		return ;

	if(periph->lock)
		spin_lock_irqsave(periph->lock, flags);

	__sunxi_clk_periph_disable(hw);
	/* if common gate exist, disable it */
	if(periph->com_gate)
		__sunxi_clk_periph_disable_shared(periph);

	if(periph->lock)
		spin_unlock_irqrestore(periph->lock, flags);
}

static unsigned long sunxi_clk_periph_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
{
	unsigned long reg, flags = 0;
	struct sunxi_clk_periph *periph = to_clk_periph(hw);
	struct sunxi_clk_periph_div *divider = &periph->divider;
	unsigned long div, div_m = 0, div_n = 0;
	u64 rate = parent_rate;

	if(!divider->reg)
		return parent_rate;

	if(periph->lock)
		spin_lock_irqsave(periph->lock, flags);

	reg = periph_readl(periph,divider->reg);
	if(divider->mwidth)
		div_m = GET_BITS(divider->mshift, divider->mwidth, reg);
	if(divider->nwidth)
		div_n = GET_BITS(divider->nshift, divider->nwidth, reg);
	div = (div_m+1)*(1<<div_n);
	do_div(rate, div);

	if(periph->lock)
		spin_unlock_irqrestore(periph->lock, flags);

	return rate;
}


static long sunxi_clk_periph_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *prate)
{
	struct sunxi_clk_periph *periph = to_clk_periph(hw);
	struct sunxi_clk_periph_div *divider = &periph->divider;
#ifdef NEW_RATE_CALULATE
	unsigned long i=0,factor_m=0,factor_n=0,found=0;
#endif
	unsigned long div, div_m = 0, div_n = 0;
	u64 parent_rate = (*prate+rate/2-1);

	do_div(parent_rate, rate);
	div = parent_rate;
	if(!div)
		return *prate;

	parent_rate = *prate;
	div_m = 1<<divider->mwidth;
	if(divider->nwidth) {
		div_n = 1<<divider->nwidth;
		div_n = 1<<(div_n-1);
	} else
		div_n = 1;
#ifndef NEW_RATE_CALULATE
	if(div <= div_m) {
		do_div(parent_rate, div);
	} else if((div <= div_m*2) && (div_n >= 2)) {
		div &= ~(1<<0);
		do_div(parent_rate, div);
	} else if((div <= div_m*4) && (div_n >= 4)) {
		div &= ~(3<<0);
		do_div(parent_rate, div);
	} else if((div <= div_m*8) && (div_n >= 8)) {
		div &= ~(7<<0);
		do_div(parent_rate, div);
	} else if((div <= div_m*16) && (div_n >= 16)) {
		div &= ~(15<<0);
		do_div(parent_rate, div);
	} else if((div <= div_m*32) && (div_n >= 32)) {
		div &= ~(31<<0);
		do_div(parent_rate, div);
	} else if((div <= div_m*64) && (div_n >= 64)) {
		div &= ~(63<<0);
		do_div(parent_rate, div);
	} else if((div <= div_m*128) && (div_n >= 128)) {
		div &= ~(127<<0);
		do_div(parent_rate, div);
	} else {
		do_div(parent_rate, div_m*div_n);
	}
#else //NEW_RATE_CALULATE
	while(i < (1<<divider->nwidth))
	{
		if(div <= div_m)
		{
			factor_m = div-1;
			factor_n = i;
			do_div(parent_rate, (factor_m+1)*(1 << factor_n));
			found = 1;
			break;
		}
		div = div >>1;
		i++;

		 if(!div)
		{
			factor_m = 0;
			factor_n = i;
			do_div(parent_rate, (factor_m+1)*(1 << factor_n));
			found = 1;
			break;
		}
	}
	if(!found)
	{
		factor_m = (div >div_m?div_m:div)-1;
		factor_n = (1<<divider->nwidth) -1;
		do_div(parent_rate, (factor_m+1)*(1 << factor_n));
	}
#endif
	return parent_rate;
}

static int __sunxi_clk_periph_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate)
{
#ifdef NEW_RATE_CALULATE
	unsigned long i=0,factor_m=0,factor_n=0,found=0;
#endif
	unsigned long reg;
	struct sunxi_clk_periph *periph = to_clk_periph(hw);
	struct sunxi_clk_periph_div *divider = &periph->divider;
	unsigned long div, div_m = 0, div_n = 0;
	u64 tmp_rate = parent_rate;

	if(periph->flags & CLK_READONLY)
		return 0;

	if(!divider->reg)
		return 0;

	do_div(tmp_rate, rate);
	div = tmp_rate;
	if(!div)
		div_m = div_n =0;
	else {
		div_m = 1<<divider->mwidth;
		div_n = (1<<divider->nwidth)-1;

		if( div > (div_m<<div_n) )
		{
			WARN(1, "clk %s rate is too large : %lu\n",hw->clk->name , rate );
			div = div_m<<div_n;
		}
#ifndef NEW_RATE_CALULATE
		if(div < div_m) {
			div_m = div;
			div_n = 0;
		} else if((div < div_m*2) && (div_n > 0)){
			div_n = 1;
			div_m = div>>1;
		} else if((div < div_m*4) && (div_n > 1)){
			div_n = 2;
			div_m = div>>2;
		} else if((div < div_m*8) && (div_n > 2)){
			div_n = 3;
			div_m = div>>3;
		} else if((div < div_m*16) && (div_n > 3)){
			div_n = 4;
			div_m = div>>4;
		} else if((div < div_m*32) && (div_n > 4)){
			div_n = 5;
			div_m = div>>5;

		} else if((div < div_m*64) && (div_n > 5)){
			div_n = 6;
			div_m = div>>6;
		} else if((div < div_m*128) && (div_n > 6)){
			div_n = 7;
			div_m = div>>7;
		} else {
			div_m = (1<<divider->mwidth);// - 1;
			div_n = (1<<divider->nwidth) - 1;
		}
		if(div_m)
			div_m--;
#else
	found = 0;
	while(i < (1<<divider->nwidth))
	{
		if(div <= div_m)
		{
			factor_m = div-1;
			factor_n = i;
			found = 1;
			break;
		}
		div = div >>1;
		i++;
		 if(!div)
		{
			factor_m = 0;
			factor_n = i;
			found = 1;
			break;
		}
	}
	if(!found)
	{
		factor_m = (div >div_m?div_m:div)-1;
		factor_n = (1<<divider->nwidth) -1;
	}
	div_m = factor_m;
	div_n = factor_n;
#endif
	}

	reg = periph_readl(periph,divider->reg);
	if(divider->mwidth)
		reg = SET_BITS(divider->mshift, divider->mwidth, reg, div_m);
	if(divider->nwidth)
		reg = SET_BITS(divider->nshift, divider->nwidth, reg, div_n);
	periph_writel(periph,reg, divider->reg);

	return 0;
}
static int sunxi_clk_periph_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate)
{
	unsigned long flags = 0;
	int ret = 0;
	struct sunxi_clk_periph *periph = to_clk_periph(hw);

	if(periph->lock)
		spin_lock_irqsave(periph->lock, flags);

	ret = __sunxi_clk_periph_set_rate(hw,rate,parent_rate);

	if(periph->lock)
		spin_unlock_irqrestore(periph->lock, flags);

	return ret;
}

static const struct clk_ops sunxi_clk_periph_ops = {

	.get_parent = sunxi_clk_periph_get_parent,
	.set_parent = sunxi_clk_periph_set_parent,

	.recalc_rate = sunxi_clk_periph_recalc_rate,
	.round_rate = sunxi_clk_periph_round_rate,
	.set_rate = sunxi_clk_periph_set_rate,

	.is_enabled = sunxi_clk_periph_is_enabled,
	.enable = sunxi_clk_periph_enable,
	.disable = sunxi_clk_periph_disable,
};

void sunxi_clk_get_periph_ops(struct clk_ops* ops)
{
	memcpy(ops,&sunxi_clk_periph_ops,sizeof(sunxi_clk_periph_ops));
}

struct clk *sunxi_clk_register_periph(struct periph_init_data *pd,
					void __iomem  *base)
{
	struct clk *clk;
	struct clk_init_data init;
	struct sunxi_clk_periph *periph;

	BUG_ON((pd == NULL) && (pd->periph == NULL));

#ifdef __SUNXI_ALL_CLK_IGNORE_UNUSED__
	pd->flags |= CLK_IGNORE_UNUSED;
#endif

	periph = pd->periph;
	init.name = pd->name;

	init.ops = periph->priv_clkops
			? periph->priv_clkops
			: (&sunxi_clk_periph_ops);

	init.flags = pd->flags;
	init.parent_names = pd->parent_names;
	init.num_parents = pd->num_parents;

	/* Data in .init is copied by clk_register(), so stack variable OK */
	periph->hw.init = &init;
	periph->flags = init.flags;

	/* fix registers */
	periph->mux.reg = periph->mux.reg ? (base
			+ (unsigned long __force)periph->mux.reg) : NULL;

	periph->divider.reg = periph->divider.reg ? (base
			+ (unsigned long __force)periph->divider.reg) : NULL;

	periph->gate.enable = periph->gate.enable ? (base
			+ (unsigned long __force)periph->gate.enable) : NULL;

	periph->gate.reset = periph->gate.reset ? (base
			+ (unsigned long __force)periph->gate.reset) : NULL;

	periph->gate.bus = periph->gate.bus ? (base
			+ (unsigned long __force)periph->gate.bus) : NULL;

	periph->gate.dram = periph->gate.dram ? (base
			+ (unsigned long __force)periph->gate.dram) : NULL;

	clk = clk_register(NULL, &periph->hw);
	if (IS_ERR(clk))
		return clk;

	return clk;
}


int sunxi_periph_reset_deassert(struct clk *c)
{
	struct clk_hw *hw = __clk_get_hw(c);
	struct sunxi_clk_periph *periph = to_clk_periph(hw);
	struct sunxi_clk_periph_gate *gate = &periph->gate;
	unsigned long reg, flag = 0;
	unsigned long flags = 0;

	if(periph->flags & CLK_READONLY)
		return 0;

	if((periph->com_gate && periph->com_gate->val)
		&& (periph->com_gate->val & periph->com_gate->mask) != (1 << periph->com_gate_off))
		return 1;

	if(periph->lock)
		spin_lock_irqsave(periph->lock, flags);

	if(gate->dram) {
		reg = periph_readl(periph,gate->dram);
		flag = GET_BITS(gate->ddr_shift, 1, reg);
		/* disable dram access */
		reg = SET_BITS(gate->ddr_shift, 1, reg, 0);
		periph_writel(periph,reg, gate->dram);
	}

	if(gate->reset) {
		reg = periph_readl(periph,gate->reset);
		reg = SET_BITS(gate->rst_shift, 1, reg, 1);
		periph_writel(periph,reg, gate->reset);
	}

	/* enable dram access if it is needed */
	if(gate->dram && flag) {
		reg = periph_readl(periph,gate->dram);
		reg = SET_BITS(gate->ddr_shift, 1, reg, 1);
		periph_writel(periph,reg, gate->dram);
	}

	if(periph->lock)
		spin_unlock_irqrestore(periph->lock, flags);

	return 0;
}

int sunxi_periph_reset_assert(struct clk *c)
{
	struct clk_hw *hw = __clk_get_hw(c);
	struct sunxi_clk_periph *periph = to_clk_periph(hw);
	struct sunxi_clk_periph_gate *gate = &periph->gate;
	unsigned long reg, flag = 0;
	unsigned long flags = 0;

	if(periph->flags & CLK_READONLY)
		return 0;

	if((periph->com_gate && periph->com_gate->val)
		&& (periph->com_gate->val & periph->com_gate->mask) != (1 << periph->com_gate_off))
		return 1;

	if(periph->lock)
		spin_lock_irqsave(periph->lock, flags);

	/* disable dram access */
	if(gate->dram) {
		reg = periph_readl(periph,gate->dram);
		flag = GET_BITS(gate->ddr_shift, 1, reg);
		reg = SET_BITS(gate->ddr_shift, 1, reg, 0);
		periph_writel(periph,reg, gate->dram);
	}

	/* assert reset of periph */
	if(gate->reset) {
		reg = periph_readl(periph,gate->reset);
		reg = SET_BITS(gate->rst_shift, 1, reg, 0);
		periph_writel(periph,reg, gate->reset);
	}
	/* enable dram access if it is needed */
	if(gate->dram && flag) {
		reg = periph_readl(periph,gate->dram);
		reg = SET_BITS(gate->ddr_shift, 1, reg, 1);
		periph_writel(periph,reg, gate->dram);
	}

	if(periph->lock)
		spin_unlock_irqrestore(periph->lock, flags);

	return 0;
}
EXPORT_SYMBOL(sunxi_periph_reset_assert);
EXPORT_SYMBOL(sunxi_periph_reset_deassert);

